//  Copyright 2008 Stefan Kristiansson
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the Lesser GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  Lesser GNU General Public License for more details.
//
//  You should have received a copy of the Lesser GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

namespace Ceme
{
    public class CemeParser
    {
        class token
        {
            public token(int type, int level, string value)
            {
                Type = type;
                Level = level;
                SValue = value;
            }
            public token(int type, int level, double value)
            {
                Type = type;
                Level = level;
                DValue = value;
            }
            public const int CONST = 0;
            public const int VAR = 1;
            public const int FUNC = 2;
            public const int BRACKETSTART = 3;
            public const int BRACKETEND = 4;
            public const int COMMA = 5;
            public const int OP_ADD = 6;
            public const int OP_SUB = 7;
            public const int OP_MUL = 8;                                               
            public const int OP_DIV = 9;
            public const int OP_POW = 10;
            public const int DELETE = 11;
            public const int NONE = 12;
            public int Type;
            public int Level;
            public double DValue;
            public string SValue;  
        }
        public delegate double ProcessFunctionCB(double[] arguments);
        class function 
        {
            public function(string name, int numArg, ProcessFunctionCB processFunction)
            {
                Name = name;
                NumArg = numArg;
                ProcessFunction = new ProcessFunctionCB(processFunction);
            }
            public string Name;
            public int NumArg;
            public ProcessFunctionCB ProcessFunction;
        }
        List<token> tokens;
        List<function> functions;
        public CemeParser()
        {
            tokens = new List<token>();
            functions = new List<function>();
            addBuiltinFunctions();
        }
        public CemeParser(string expression)
        {
            tokens = new List<token>();
            SetExpression(expression);
            functions = new List<function>();
            addBuiltinFunctions();
        }
        public void SetExpression(string expression) 
        {
            string[] sTokens = tokenize(expression);
            tokens = classify(sTokens);
        }
        public double Evaluate() 
        {
            double value = 0;
            int level = getHighestLevel(tokens);            
            while (level >= 0)
            {
                List<token> levelTokens = tokens.FindAll(delegate(token t) { return (t.Level == level); });
                while (levelTokens.Count > 0) {
                    value = processLevel(levelTokens);
                    tokens.RemoveAll(delegate(token t) { return (t.Type == token.DELETE); });
                    levelTokens = tokens.FindAll(delegate(token t) { return (t.Level == level); });
                }
                level--;
            }
            return value;
        }
        public string[] GetVariables()
        {
            List<token> varList = tokens.FindAll(delegate(token t) { return (t.Type == token.VAR); });
            List<string> sVarList = new List<string>();

            varList.ForEach(delegate(token t) {
                if (sVarList.FindAll(delegate(string sVar) { return (sVar == t.SValue); }).Count == 0)
                    sVarList.Add(t.SValue);
            });
            return sVarList.ToArray();
        }
        public bool SetVariable(string variable, double value)
        {
            int i = tokens.FindIndex(delegate(token t) { return (t.Type == token.VAR && t.SValue == variable); });
            if (i < 0)
                return false;
            while (i >= 0)
            {
                tokens[i].DValue = value;
                i = tokens.FindIndex(i+1,delegate(token t) { return (t.Type == token.VAR && t.SValue == variable); });
            }
            return true;
        }        
        public bool AddFunction(string name, int numArg, ProcessFunctionCB function) 
        {
            functions.Add(new function(name, numArg, function));
            return true;
        }
        
        #region builtin functions
        private void addBuiltinFunctions()
        {
            AddFunction("sin", 1, funcSin);
            AddFunction("cos", 1, funcCos);
            AddFunction("tan", 1, funcTan);
            AddFunction("acos", 1, funcAcos);
            AddFunction("asin", 1, funcAsin);
            AddFunction("atan", 1, funcAtan);
            AddFunction("atan2", 2, funcAtan2);
            AddFunction("pow", 2, funcPow);
            AddFunction("sqrt", 1, funcSqrt);
            AddFunction("exp", 1, funcExp);
            AddFunction("abs", 1, funcAbs);
            AddFunction("log", 1, funcLog);
            AddFunction("log10", 1, funcLog10);

        }
        private double funcSin(double[] args)
        {
            return Math.Sin(args[0] * (Math.PI / 180));
        }
        private double funcCos(double[] args)
        {
            return Math.Cos(args[0] * (Math.PI / 180));
        }
        private double funcTan(double[] args)
        {
            return Math.Tan(args[0] * (Math.PI / 180));
        }
        private double funcAcos(double[] args)
        {
            return Math.Acos(args[0]) * (180 / Math.PI);
        }
        private double funcAsin(double[] args)
        {
            return Math.Asin(args[0]) * (180 / Math.PI);
        }
        private double funcAtan(double[] args)
        {
            return Math.Atan(args[0]) * (180 / Math.PI);
        }
        private double funcAtan2(double[] args)
        {
            return Math.Atan2(args[0], args[1]) * (180 / Math.PI);
        }
        private double funcPow(double[] args)
        {
            return Math.Pow(args[0],args[1]);
        }
        private double funcSqrt(double[] args)
        {
            return Math.Sqrt(args[0]);
        }
        private double funcExp(double[] args)
        {
            return Math.Exp(args[0]);
        }
        private double funcAbs(double[] args)
        {
            return Math.Abs(args[0]);
        }
        private double funcLog(double[] args)
        {
            return Math.Log(args[0]);
        }
        private double funcLog10(double[] args)
        {
            return Math.Log10(args[0]);
        }
        
        #endregion

        private double processLevel(List<token> tokens)
        {  
            if (tokens[0].Type == token.FUNC)
            {
                int stop = tokens.FindIndex(delegate(token t) { return (t.Type == token.BRACKETEND);});
                tokens[stop].Type = token.DELETE;
                return processFunction(tokens.GetRange(0,stop));
            }
            else if (tokens[0].Type == token.BRACKETSTART)
            {
                tokens[0].Type = token.DELETE;
                int stop = tokens.FindIndex(delegate(token t) { return (t.Type == token.BRACKETEND); });
                tokens[stop].Type = token.DELETE;
                return processArithmetic(tokens.GetRange(1,stop));
            }
            else
            {
                return processArithmetic(tokens);
            }
        }
        private double processFunction(List<token> tokens) 
        {
            function func = functions.FindLast(delegate(function f) { return (tokens[0].SValue.ToLower() == f.Name.ToLower()); });
            List<double> args = new List<double>();
            int j;
            if (func == null)
                throw new Exception("Function does not exist");
            int start = 2;
            int i = tokens.FindIndex(delegate(token t) { return (t.Type == token.COMMA); });
            while (i >= 0)
            {
                if (i - start > 0)
                    args.Add(processArithmetic(tokens.GetRange(start, i - start)));

                for (j = start; j <= i; j++)
                    tokens[j].Type = token.DELETE;

                start = i + 1;
                i = tokens.FindIndex(i+1,delegate(token t) { return (t.Type == token.COMMA); });
            }
            if (tokens.Count - start > 0)
                args.Add(processArithmetic(tokens.GetRange(start, tokens.Count - start)));

            for (j=start;j<tokens.Count;j++)
                tokens[j].Type = token.DELETE;
            
            if (args.Count != func.NumArg)
                throw new Exception("Invalid number of arguments");
            tokens[1].Type = token.DELETE;
            tokens[0].Type = token.CONST;
            tokens[0].DValue = func.ProcessFunction(args.ToArray());
            tokens[0].Level--;
            tokens.RemoveAll(delegate(token t) { return (t.Type == token.DELETE); });
            return tokens[0].DValue;
        }
        private double processArithmetic(List<token> tokens)
        {
            
            processOp(tokens, token.OP_POW, token.NONE);
            processOp(tokens, token.OP_MUL, token.OP_DIV);
            processOp(tokens, token.OP_ADD, token.OP_SUB);
            if (tokens.Count == 0)
                throw new Exception("Invalid equation");
            tokens[0].Level--;
            return tokens[0].DValue;
        }

        private void processOp(List<token> tokens, int op1, int op2)
        {
            int index = tokens.FindIndex(delegate(token t) { return (t.Type == op1 || t.Type == op2); });
            while (index != -1)
            {
                double leftop = 0;
                double rightop = 0;
                int neg = 1;

                if (index > 0)
                {

                    if (tokens[index - 1].Type == token.CONST || tokens[index - 1].Type == token.VAR)
                    {
                        leftop = tokens[index - 1].DValue;
                        tokens[index - 1].Type = token.DELETE;
                    } else {
                        throw new Exception("Invalid equation");
                    }
                }

                int stop = tokens.FindIndex(index + 1, delegate(token t) { return (t.Type == token.VAR || t.Type == token.CONST); });
                if (stop < 0)
                    throw new Exception("Invalid equation");

                for (int i = 1; i < stop - index; i++)
                {
                    if (tokens[index + i].Type == token.OP_SUB)
                    {
                        neg *= -1;
                        tokens[index + i].Type = token.DELETE;
                    }
                }
                rightop = neg * tokens[stop].DValue;
                tokens[stop].Type = token.DELETE;

                switch (tokens[index].Type)
                {
                    case token.OP_POW:
                        tokens[index].DValue = Math.Pow(leftop, rightop);
                        break;
                    case token.OP_MUL:
                        tokens[index].DValue = leftop * rightop;
                        break;
                    case token.OP_DIV:
                        tokens[index].DValue = leftop / rightop;
                        break;
                    case token.OP_ADD:
                        tokens[index].DValue = leftop + rightop;
                        break;
                    case token.OP_SUB:
                        tokens[index].DValue = leftop - rightop;
                        break;
                }
                tokens[index].Type = token.CONST;
                tokens.RemoveAll(delegate(token t) { return (t.Type == token.DELETE); });
                index = tokens.FindIndex(delegate(token t) { return (t.Type == op1 || t.Type == op2); });
            }
        }
        private int getHighestLevel(List<token> tokens)
        {
            int level = tokens[0].Level;
            for (int i = 0; i < tokens.Count; i++)
            {
                if (tokens[i].Level > level)
                    level = tokens[i].Level;
            }
            return level;
        }
        private string[] tokenize(string equation)
        {
            Regex re = new Regex(@"([\/\,\+\-\*\(\)\^\\])");
            equation = equation.Replace(" ","");
            return (re.Split(equation));
        }
        private List<token> classify(string[] sTokens) {
            List<token> tokens = new List<token>();
            int level = 0;
            for (int i = 0; i < sTokens.Length;i++ )
            {
                if (sTokens[i] != "")
                {
                    if (Regex.IsMatch(sTokens[i][0].ToString(), "[0-9]"))
                    {
                        tokens.Add(new token(token.CONST, level, Double.Parse(sTokens[i])));
                    }
                    if (Regex.IsMatch(sTokens[i][0].ToString(), "[a-z]", RegexOptions.IgnoreCase))
                    {
                        if (i < sTokens.Length - 1)
                        {
                            if (sTokens[i + 1][0] == '(')
                            {
                                tokens.Add(new token(token.FUNC, level + 1, sTokens[i]));
                            }
                            else
                            {
                                tokens.Add(new token(token.VAR, level, sTokens[i]));
                            }
                        }
                        else
                        {
                            tokens.Add(new token(token.VAR, level, sTokens[i]));
                        }
                    }
                    switch (sTokens[i][0])
                    {
                        case '(':
                            tokens.Add(new token(token.BRACKETSTART, ++level ,sTokens[i]));
                            break;
                        case ')':
                            tokens.Add(new token(token.BRACKETEND, level--, sTokens[i]));
                            break;
                        case '+':
                            tokens.Add(new token(token.OP_ADD, level, sTokens[i]));
                            break;
                        case '-':
                            tokens.Add(new token(token.OP_SUB, level, sTokens[i]));
                            break;
                        case '*':
                            tokens.Add(new token(token.OP_MUL, level, sTokens[i]));
                            break;
                        case '/':
                            tokens.Add(new token(token.OP_DIV, level, sTokens[i]));
                            break;
                        case '^':
                            tokens.Add(new token(token.OP_POW, level, sTokens[i]));
                            break;
                        case ',':
                            tokens.Add(new token(token.COMMA, level, sTokens[i]));
                            break;
                    }
                }
            }
            if (level != 0)
                throw new Exception("Invalid number of brackets in expression");
            return tokens;
        }

    }
}
